﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ComponentModel;
using Agility.Wcf.ClientProxy.RequirementsManagementServiceClient;

namespace Agility.Wcf.ClientProxy
{
	public class AgilityClientProxy : IDisposable, INotifyPropertyChanged
	{
		private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
		private static readonly AgilityClientProxy proxy = new AgilityClientProxy();

		private AuthenticationServiceClient.AuthenticationServiceSoapClient authenticationProxy;
		private RequirementsManagementServiceClient.RequirementsManagementServiceSoapClient requirementsManagementProxy;
		private bool isLoggedOn;
		private bool keepMeLoggedOn;
		private Uri agilityUrl;
		private List<Epic> originalIdeas;

		public event PropertyChangedEventHandler PropertyChanged;

		// TODO: consider if we need a way to change the agility URL
		
		private AgilityClientProxy()
		{
			logger.Debug("--> Entering ctor");
			originalIdeas = new List<Epic>();
			logger.Debug("<-- Leaving ctor");
		}

		public static AgilityClientProxy GetInstance(Uri agilityUrl)
		{
			logger.Debug("--> Entering GetInstance");
			if (proxy.AgilityUrl == null)
			{
				proxy.isLoggedOn = false;
				proxy.disposed = false;
				proxy.AgilityUrl = agilityUrl;
				logger.Debug("Set the base URL to connect with Agility");
				proxy.Connect();
			} 
			logger.Debug("<-- Leaving GetInstance");
			return proxy;
		}

		public bool KeepMeLoggedOn 
		{
			get { return this.keepMeLoggedOn; }
			set
			{
				this.keepMeLoggedOn = value;
				if (this.keepMeLoggedOn && 
					!string.IsNullOrEmpty(Properties.Settings.Default.Cookie) && 
					string.IsNullOrEmpty(((CookieManagerEndpointBehavior)authenticationProxy.Endpoint.Behaviors[typeof(CookieManagerEndpointBehavior)]).CookieManager.AuthenticationCookie))
				{
					int startIndex = Properties.Settings.Default.Cookie.IndexOf("expires=") + 8;
					int endIndex = Properties.Settings.Default.Cookie.IndexOf(';', startIndex);
					string dateString = Properties.Settings.Default.Cookie.Substring(startIndex, endIndex - startIndex);
					DateTime expiryDate = DateTime.Parse(dateString);
					if (expiryDate > DateTime.Now)
					{
						logger.Debug("Cookie is still valid");
						((CookieManagerEndpointBehavior)authenticationProxy.Endpoint.Behaviors[typeof(CookieManagerEndpointBehavior)]).CookieManager.AuthenticationCookie = Properties.Settings.Default.Cookie;
						logger.Debug("Using cookie from previous session");
						this.IsLoggedOn = true;
					}
				}
				OnNotifyPropertyChanged("KeepMeLoggedOn");
			}
		}

		public bool IsLoggedOn 
		{
			get { return this.isLoggedOn; }
			private set
			{
				this.isLoggedOn = value;
				OnNotifyPropertyChanged("IsLoggedOn");
			}
		}

		public Uri AgilityUrl 
		{
			get { return this.agilityUrl; }
			private set
			{
				this.agilityUrl = value;
				this.OnNotifyPropertyChanged("AgilityUrl");
			}
		}
		
		private void OnNotifyPropertyChanged(string propertyName)
		{
			logger.Debug("--> Entering OnNotifyPropertyChanged");
			if (this.PropertyChanged != null)
			{
				this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
				logger.Debug("Raised PropertyChanged event for property {0}", propertyName);
			}
			logger.Debug("<-- Leaving OnNotifyPropertyChanged");
		}

		private void Connect()
		{
			logger.Debug("--> Entering Connect");
			if (proxy.AgilityUrl == null)
			{
				throw new InvalidOperationException("AgilityUrl is not set");
			}
			bool useSsl = proxy.AgilityUrl.AbsoluteUri.StartsWith("https", StringComparison.OrdinalIgnoreCase);
			try
			{
				System.ServiceModel.BasicHttpBinding binding;
				if (useSsl)
				{
					binding = new System.ServiceModel.BasicHttpBinding(System.ServiceModel.BasicHttpSecurityMode.Transport);
					logger.Debug("Using SSL to secure the connection on transport level");
				}
				else
				{
					binding = new System.ServiceModel.BasicHttpBinding(System.ServiceModel.BasicHttpSecurityMode.None);
					logger.Debug("Using no security during transfer");
				}
				binding.AllowCookies = false;
				EndpointAddress authEndpoint = new EndpointAddress(string.Format(CultureInfo.InvariantCulture, "{0}Agility-Web-AuthenticationService.svc/Soap", this.AgilityUrl));
				this.authenticationProxy = new AuthenticationServiceClient.AuthenticationServiceSoapClient(binding, authEndpoint);
				CookieManagerEndpointBehavior cookieMgmtBehavior = new CookieManagerEndpointBehavior();
				this.authenticationProxy.Endpoint.Behaviors.Add(cookieMgmtBehavior);
				logger.Debug("Created client proxy for Agility authentication service");
				EndpointAddress reqMgmtEndpoint = new EndpointAddress(string.Format(CultureInfo.InvariantCulture,"{0}Agility-Web-Services-RequirementsManagementService.svc/Soap", this.AgilityUrl));
				this.requirementsManagementProxy = new RequirementsManagementServiceClient.RequirementsManagementServiceSoapClient(binding, reqMgmtEndpoint);
				this.requirementsManagementProxy.Endpoint.Behaviors.Add(cookieMgmtBehavior);
				logger.Debug("Created client proxy for Agility requirements-management service");
			}
			catch (Exception ex)
			{
				logger.Error(ex.ToString());
				throw;
			}
			logger.Debug("<-- Leaving Connect");
		}

		public void LogOn(string userName, string password)
		{
			logger.Debug("--> Entering LogOn");
			if (string.IsNullOrEmpty(userName))
			{
				throw new ArgumentNullException(userName);
			}
			if (string.IsNullOrEmpty(password))
			{
				throw new ArgumentNullException(password);
			}
			AuthenticationServiceClient.QueryResultOfUser result = authenticationProxy.Login(userName, password, this.KeepMeLoggedOn, string.Empty); 
			if (result.RootResults != null)
			{
				logger.Info("Successfully logged into Agility");
				this.IsLoggedOn = true;
			}
			logger.Debug("<-- Leaving LogOn");
		}

		public void LogOff()
		{
			logger.Debug("--> Entering LogOff");
			AuthenticationServiceClient.QueryResultOfUser result = authenticationProxy.Logout();
			this.IsLoggedOn = false;
			logger.Info("Successfully logged off from Agility");
			logger.Debug("<-- Leaving LogOff");
		}

		public List<Epic> GetMyIdeas()
		{
			logger.Debug("--> Entering GetMyEpics");
			RequirementsManagementServiceClient.QueryResultOfEpic result = requirementsManagementProxy.GetMyIdeas();
			logger.Info("Retrieved {0} ideas/epics", result.RootResults.Count);
			if (logger.IsTraceEnabled)
			{
				foreach (Epic epic in result.RootResults)
				{
					logger.Trace("{0}", epic.ToString());
				}
			}

			this.originalIdeas = result.RootResults;
			logger.Debug("<-- Leaving GetMyEpics");
			return result.RootResults;
		}

		public void SubmitChanges(IList<Epic> ideas)
		{
			logger.Debug("--> Entering UpdateMyIdeas");
			List<ChangeSetEntry> changeSet = new List<ChangeSetEntry>();
			foreach (Epic idea in ideas)
			{
				Epic originalIdea = this.originalIdeas.FirstOrDefault(e => e.Id == idea.Id);
				if (originalIdea == null)
				{
					ChangeSetEntry entry = new ChangeSetEntry();
					entry.Entity = idea;
					entry.Operation = DomainOperation.Insert;
					changeSet.Add(entry);
				}
				else if (!idea.Equals(originalIdea))
				{
					ChangeSetEntry entry = new ChangeSetEntry();
					entry.Entity = idea;
					entry.OriginalEntity = originalIdea;
					entry.Operation = DomainOperation.Update;
					changeSet.Add(entry);
				}
			}
			this.requirementsManagementProxy.SubmitChanges(changeSet);
			logger.Debug("<-- Leaving UpdateMyIdeas");
		}

		#region Disposable implementation
		bool disposed;

		public void Close()
		{
			this.Dispose();
		}

		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		 ~AgilityClientProxy()
		{
			Dispose(false);
		}

		protected virtual void Dispose(bool disposing)
		{
			if (!disposed)
			{
				if (this.KeepMeLoggedOn)
				{
					Properties.Settings.Default.Cookie = ((CookieManagerEndpointBehavior)authenticationProxy.Endpoint.Behaviors[typeof(CookieManagerEndpointBehavior)]).CookieManager.AuthenticationCookie;
				}
				else
				{
					Properties.Settings.Default.Cookie = string.Empty;
				}
				Properties.Settings.Default.Save();
				logger.Debug("Saved the cookie");
				if (disposing)
				{
					// dispose of all managed resources here
					if (authenticationProxy.State != CommunicationState.Closed)
					{
						authenticationProxy.Close();
					}
				}

				// release any unmanaged resources here
				this.agilityUrl = null;
				this.disposed = true;
			}
		}
		#endregion

	}
}
